from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog
import cv2, math
import numpy as np

class Analysis(QtWidgets.QWidget):
    def __init__(self, image):
        super().__init__()
        self.setObjectName("Analysis")
        self.setWindowTitle("Analysis")
        self.resize(1096, 508)
        self.gridLayout = QtWidgets.QGridLayout(self)
        self.gridLayout.setContentsMargins(11, 11, 11, 11)
        self.gridLayout.setSpacing(6)
        self.gridLayout.setObjectName("gridLayout")
        self.verticalLayout_4 = QtWidgets.QVBoxLayout()
        self.verticalLayout_4.setSpacing(6)
        self.verticalLayout_4.setObjectName("verticalLayout_4")
        self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_9.setSpacing(6)
        self.horizontalLayout_9.setObjectName("horizontalLayout_9")
        self.high_ratio_label = QtWidgets.QLabel(self)
        self.high_ratio_label.setMinimumSize(QtCore.QSize(40, 0))
        self.high_ratio_label.setObjectName("high_ratio_label")
        self.horizontalLayout_9.addWidget(self.high_ratio_label)
        self.high_ratio_qslider = QtWidgets.QSlider(self)
        self.high_ratio_qslider.setValue(100)
        self.high_ratio_qslider.setMinimumSize(QtCore.QSize(200, 20))
        self.high_ratio_qslider.setOrientation(QtCore.Qt.Horizontal)
        self.high_ratio_qslider.setObjectName("high_ratio_qslider")
        self.horizontalLayout_9.addWidget(self.high_ratio_qslider)
        self.high_ratio_spinbox = QtWidgets.QSpinBox(self)
        self.high_ratio_spinbox.setValue(100)
        self.high_ratio_spinbox.setMinimumSize(QtCore.QSize(71, 31))
        self.high_ratio_spinbox.setObjectName("high_ratio_spinbox")
        self.horizontalLayout_9.addWidget(self.high_ratio_spinbox)
        self.verticalLayout_4.addLayout(self.horizontalLayout_9)
        self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_10.setSpacing(6)
        self.horizontalLayout_10.setObjectName("horizontalLayout_10")
        self.high_grade_label = QtWidgets.QLabel(self)
        self.high_grade_label.setObjectName("high_grade_label")
        self.horizontalLayout_10.addWidget(self.high_grade_label)
        self.high_grade_qslider = QtWidgets.QSlider(self)
        self.high_grade_qslider.setValue(50)
        self.high_grade_qslider.setMinimumSize(QtCore.QSize(200, 20))
        self.high_grade_qslider.setOrientation(QtCore.Qt.Horizontal)
        self.high_grade_qslider.setObjectName("high_grade_qslider")
        self.horizontalLayout_10.addWidget(self.high_grade_qslider)
        self.high_grade_spinbox = QtWidgets.QSpinBox(self)
        self.high_grade_spinbox.setValue(50)
        self.high_grade_spinbox.setMinimumSize(QtCore.QSize(71, 31))
        self.high_grade_spinbox.setObjectName("high_grade_spinbox")
        self.horizontalLayout_10.addWidget(self.high_grade_spinbox)
        self.verticalLayout_4.addLayout(self.horizontalLayout_10)
        self.gridLayout.addLayout(self.verticalLayout_4, 0, 2, 1, 1)
        self.verticalLayout_5 = QtWidgets.QVBoxLayout()
        self.verticalLayout_5.setSpacing(6)
        self.verticalLayout_5.setObjectName("verticalLayout_5")
        self.horizontalLayout_11 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_11.setSpacing(6)
        self.horizontalLayout_11.setObjectName("horizontalLayout_11")
        self.low_ratio_label = QtWidgets.QLabel(self)
        self.low_ratio_label.setMinimumSize(QtCore.QSize(40, 0))
        self.low_ratio_label.setObjectName("low_ratio_label")
        self.horizontalLayout_11.addWidget(self.low_ratio_label)
        self.low_ratio_qslider = QtWidgets.QSlider(self)
        self.low_ratio_qslider.setValue(100)
        self.low_ratio_qslider.setMinimumSize(QtCore.QSize(200, 20))
        self.low_ratio_qslider.setOrientation(QtCore.Qt.Horizontal)
        self.low_ratio_qslider.setObjectName("low_ratio_qslider")
        self.horizontalLayout_11.addWidget(self.low_ratio_qslider)
        self.low_ratio_spinbox = QtWidgets.QSpinBox(self)
        self.low_ratio_spinbox.setValue(100)
        self.low_ratio_spinbox.setMinimumSize(QtCore.QSize(71, 31))
        self.low_ratio_spinbox.setObjectName("low_ratio_spinbox")
        self.horizontalLayout_11.addWidget(self.low_ratio_spinbox)
        self.verticalLayout_5.addLayout(self.horizontalLayout_11)
        self.horizontalLayout_12 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_12.setSpacing(6)
        self.horizontalLayout_12.setObjectName("horizontalLayout_12")
        self.low_grade_label = QtWidgets.QLabel(self)
        self.low_grade_label.setObjectName("low_grade_label")
        self.horizontalLayout_12.addWidget(self.low_grade_label)
        self.low_grade_qslider = QtWidgets.QSlider(self)
        self.low_grade_qslider.setValue(50)
        self.low_grade_qslider.setMinimumSize(QtCore.QSize(200, 20))
        self.low_grade_qslider.setOrientation(QtCore.Qt.Horizontal)
        self.low_grade_qslider.setObjectName("low_grade_qslider")
        self.horizontalLayout_12.addWidget(self.low_grade_qslider)
        self.low_grade_spinbox = QtWidgets.QSpinBox(self)
        self.low_grade_spinbox.setValue(50)
        self.low_grade_spinbox.setMinimumSize(QtCore.QSize(71, 31))
        self.low_grade_spinbox.setObjectName("low_grade_spinbox")
        self.horizontalLayout_12.addWidget(self.low_grade_spinbox)
        self.verticalLayout_5.addLayout(self.horizontalLayout_12)
        self.gridLayout.addLayout(self.verticalLayout_5, 0, 4, 1, 1)
        self.graphicsView = QtWidgets.QGraphicsView(self)
        self.graphicsView.setObjectName("graphicsView")
        self.gridLayout.addWidget(self.graphicsView, 1, 0, 1, 5)
        spacerItem = QtWidgets.QSpacerItem(25, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout.addItem(spacerItem, 0, 1, 1, 1)
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setSpacing(6)
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_3.setSpacing(6)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.band_ratio_label = QtWidgets.QLabel(self)
        self.band_ratio_label.setMinimumSize(QtCore.QSize(40, 0))
        self.band_ratio_label.setObjectName("band_ratio_label")
        self.horizontalLayout_3.addWidget(self.band_ratio_label)
        self.band_ratio_qslider = QtWidgets.QSlider(self)
        self.band_ratio_qslider.setValue(100)
        self.band_ratio_qslider.setMinimumSize(QtCore.QSize(200, 20))
        self.band_ratio_qslider.setOrientation(QtCore.Qt.Horizontal)
        self.band_ratio_qslider.setObjectName("band_ratio_qslider")
        self.horizontalLayout_3.addWidget(self.band_ratio_qslider)
        self.band_ratio_spinbox = QtWidgets.QSpinBox(self)
        self.band_ratio_spinbox.setValue(100)
        self.band_ratio_spinbox.setMinimumSize(QtCore.QSize(71, 31))
        self.band_ratio_spinbox.setObjectName("band_ratio_spinbox")
        self.horizontalLayout_3.addWidget(self.band_ratio_spinbox)
        self.verticalLayout.addLayout(self.horizontalLayout_3)
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_4.setSpacing(6)
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.band_grade_label = QtWidgets.QLabel(self)
        self.band_grade_label.setObjectName("band_grade_label")
        self.horizontalLayout_4.addWidget(self.band_grade_label)
        self.band_grade_qslider = QtWidgets.QSlider(self)
        self.band_grade_qslider.setValue(50)
        self.band_grade_qslider.setMinimumSize(QtCore.QSize(200, 20))
        self.band_grade_qslider.setOrientation(QtCore.Qt.Horizontal)
        self.band_grade_qslider.setObjectName("band_grade_qslider")
        self.horizontalLayout_4.addWidget(self.band_grade_qslider)
        self.band_grade_spinbox = QtWidgets.QSpinBox(self)
        self.band_grade_spinbox.setValue(50)
        self.band_grade_spinbox.setMinimumSize(QtCore.QSize(71, 31))
        self.band_grade_spinbox.setObjectName("band_grade_spinbox")
        self.horizontalLayout_4.addWidget(self.band_grade_spinbox)
        self.verticalLayout.addLayout(self.horizontalLayout_4)
        self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
        self.save_button = QtWidgets.QPushButton("save")
        self.gridLayout.addWidget(self.save_button, 0, 5, 1, 1)
        spacerItem1 = QtWidgets.QSpacerItem(25, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout.addItem(spacerItem1, 0, 3, 1, 1)
        self.images = []
        self.images = image
        self.retranslateUi()
        self.setTabOrder(self.band_ratio_qslider, self.graphicsView)

    def retranslateUi(self):
        self.high_ratio_label.setText("high_ratio")
        self.high_grade_label.setText("high_grade")
        self.low_ratio_label.setText("low_ratio")
        self.low_grade_label.setText("low_grade")
        self.band_ratio_label.setText("band_ratio")
        self.band_grade_label.setText("band_grade")
        self.scene = QtWidgets.QGraphicsScene()
        self.save_button.clicked.connect(self.save_png_image)
        self.low_ratio_qslider.valueChanged.connect(
            lambda: self.update_spinbox(self.low_ratio_spinbox, self.low_ratio_qslider))
        self.low_ratio_spinbox.valueChanged.connect(
            lambda: self.update_slider(self.low_ratio_qslider, self.low_ratio_spinbox))
        # 绑定 low_grade_qslider 和 low_grade_spinbox
        self.low_grade_qslider.valueChanged.connect(
            lambda: self.update_spinbox(self.low_grade_spinbox, self.low_grade_qslider))
        self.low_grade_spinbox.valueChanged.connect(
            lambda: self.update_slider(self.low_grade_qslider, self.low_grade_spinbox))
        # 绑定 high_ratio_qslider 和 high_ratio_spinbox
        self.high_ratio_qslider.valueChanged.connect(
            lambda: self.update_spinbox(self.high_ratio_spinbox, self.high_ratio_qslider))
        self.high_ratio_spinbox.valueChanged.connect(
            lambda: self.update_slider(self.high_ratio_qslider, self.high_ratio_spinbox))
        # 绑定 high_grade_qslider 和 high_grade_spinbox
        self.high_grade_qslider.valueChanged.connect(
            lambda: self.update_spinbox(self.high_grade_spinbox, self.high_grade_qslider))
        self.high_grade_spinbox.valueChanged.connect(
            lambda: self.update_slider(self.high_grade_qslider, self.high_grade_spinbox))
        # 绑定 band_ratio_qslider 和 band_ratio_spinbox
        self.band_ratio_qslider.valueChanged.connect(
            lambda: self.update_spinbox(self.band_ratio_spinbox, self.band_ratio_qslider))
        self.band_ratio_spinbox.valueChanged.connect(
            lambda: self.update_slider(self.band_ratio_qslider, self.band_ratio_spinbox))
        # 绑定 band_grade_qslider 和 band_grade_spinbox
        self.band_grade_qslider.valueChanged.connect(
            lambda: self.update_spinbox(self.band_grade_spinbox, self.band_grade_qslider))
        self.band_grade_spinbox.valueChanged.connect(
            lambda: self.update_slider(self.band_grade_qslider, self.band_grade_spinbox))
        self.update_image()

    def update_spinbox(self, spinbox, slider):
        spinbox.setValue(slider.value())
        self.update_image()

    def update_slider(self, slider, spinbox):
        slider.setValue(spinbox.value())
        self.update_image()

    def save_png_image(self):
        combined_image = self.combine_all_images()
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save Image", "",
            "PNG Files (*.png);;All Files (*)", options=options
        )
        if file_path:  # 确保用户选择了文件路径
            # 使用 OpenCV 保存图片
            cv2.imwrite(file_path, combined_image)

    def compute_histogram0(self, image):
        hist = cv2.calcHist([image], [0], None, [256], [0, 256])
        hist = cv2.normalize(hist, hist).flatten()

        hist_image = np.zeros((288, 384), dtype=np.uint8)

        for x, y in enumerate(hist):
            cv2.line(hist_image, (x + 50, 256), (x + 50, 256 - int(y * 256)), 255, 1)

        cv2.line(hist_image, (50, 256), (306, 256), 255, 2)  # X-axis
        cv2.line(hist_image, (50, 256), (50, 0), 255, 2)  # Y-axis

        for i in range(0, 256, 32):
            cv2.putText(hist_image, str(i), (45 + i, 275), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255, 1)
            cv2.line(hist_image, (50 + i, 256), (50 + i, 261), 255, 1)

        for j, i in enumerate([0, 0.33, 0.66, 1]):
            cv2.putText(hist_image, str(i), (10, 256 - j * 82), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255, 1)
            cv2.line(hist_image, (45, 256 - j * 82), (50, 256 - j * 82), 255, 1)

        return hist_image

    def compute_histogram(self, image, x, y, new_radius):
        if x == None or y == None or new_radius== None:
            return self.compute_histogram0(image)

        y_start = max(y - new_radius, 0)
        x_start = max(x - new_radius, 0)

        y_end = min(y + new_radius, image.shape[0])
        x_end = min(x + new_radius, image.shape[1])
        # 创建一个与图像大小相同的掩码，初始化为黑色
        # mask = np.zeros_like(image, dtype=np.uint8)

        # 在掩码上绘制一个白色圆形区域
        # cv2.circle(mask, (x, y), r, 255, thickness=-1)

        # 使用掩码计算直方图
        histSize = 256
        histRange = (0, 256)
        hist = cv2.calcHist([image[y_start:y_end, x_start:x_end]], [0], None, [histSize], histRange)

        # 归一化直方图
        hist_h = 288-30
        # cv2.normalize(hist, hist, alpha=0, beta=hist_h, norm_type=cv2.NORM_MINMAX)

        # 创建用于绘制直方图的图像
        hist_w = 384-30
        bin_w = int(round(hist_w / histSize))

        img_h, img_w = 288, 384
        histImage = np.zeros((img_h, img_w), dtype=np.uint8)

        # 绘制直方图
        for i in range(1, histSize):
            cv2.line(histImage,
                    (bin_w * (i - 1)+50, hist_h - int(hist[i - 1])),
                    (bin_w * (i)+50, hist_h - int(hist[i])),
                    255, thickness=2)

        cv2.line(histImage, (50, img_h-30), (img_w, img_h-30), 255, 2)  # X-axis
        cv2.line(histImage, (50, img_h-30), (50, 0), 255, 2)  # Y-axis

        # X-axis
        for i, j in enumerate(range(0, 288, 32)):
            cv2.putText(histImage, str(i*32), (45 + j, 275), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255, 1)
            cv2.line(histImage, (50 + j, 256), (50 + j, 261), 255, 1)

        # Y-axis
        # for j, i in enumerate([0, 0.33, 0.66, 1]):
        #     cv2.putText(histImage, str(i), (10, 256 - j * 82), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255, 1)
        #     cv2.line(histImage, (45, 256 - j * 82), (50, 256 - j * 82), 255, 1)

        return histImage

    def compute_gradient(self, image):
        grad_x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
        grad_y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)
        gradient = cv2.magnitude(grad_x, grad_y)
        return gradient

    def filter_DCT(self, image, ratio, grade, flag="band"):
        dct_image = cv2.dct(np.float32(image))
        span = math.sqrt(dct_image.shape[0] ** 2 + dct_image.shape[1] ** 2) / ratio
        rows, cols = dct_image.shape
        x, y = np.indices((rows, cols))
        distances = np.sqrt(x ** 2 + y ** 2)
        if flag == "band":
            mask = (span * (grade - (ratio / 5)) < distances) & (distances < span * grade)
        elif flag == "low":
            mask = distances < span * grade
        elif flag == "high":
            mask = distances > span * grade
        filtered_dct = np.where(mask, dct_image, 0)
        return cv2.idct(np.float32(filtered_dct))

    def circular_external_zero(self, image, raw_image, i):
        """
        如果检测到image有唯一圆，则把
        :param image:
        :param raw_image:
        :return:
        """
        circles = cv2.HoughCircles(
            image,
            cv2.HOUGH_GRADIENT,
            dp=1.5,  # 累加器分辨率与原图的比值
            minDist=384,  # 圆心之间的最小距离
            param1=120,  # Canny 边缘检测的高阈值
            param2=30,  # 累加器阈值，小于这个值的圆会被忽略
            minRadius=20,  # 最小半径
            maxRadius=200  # 最大半径
        )
        # 4. 如果检测到圆形，处理第一个圆
        if circles is not None:
            circles = np.round(circles[0, :]).astype("int")
            x, y, r = circles[0]  # 取出第一个圆的圆心和半径

            # 5. 增加安全距离
            safety_margin = 10  # 安全距离，可以根据需要调整
            new_radius = r + safety_margin
            # 6. 创建掩膜并保留扩展后的圆内区域，置零圆外区域
            result = np.zeros_like(image)

            y_start = max(y - new_radius, 0)
            x_start = max(x - new_radius, 0)

            y_end = min(y + new_radius, image.shape[0])
            x_end = min(x + new_radius, image.shape[1])

            mask = image[y_start:y_end, x_start:x_end]
            mask = cv2.equalizeHist(mask)

            result[y_start:y_end, x_start:x_end] = mask
            # cv2.circle(mask, (x, y), new_radius, (255, 255, 255), -1)
            # if i == 4:
            #     tesdata = raw_image[y-r:y+r, x-r:x+r]
            #     np.save('./test.npy', tesdata)
            # 将掩膜和原图进行与操作，保留扩展后的圆内区域
            # result = cv2.bitwise_and(image, mask)
            return result, x, y, new_radius
        return image, None, None, None

    def combine_images(self, image, raw_image, i):
        """
        输入一张灰度图片，输出该图片的6重图：原图，直方图，梯度图，带通滤波图，高通滤波图，低通滤波图
        :param image: 8bit灰度图
        :param raw_image: 16bit灰度图
        :return: 6张图合并后的图
        """
        cols = 6
        rows = 1
        combined_image = np.zeros((288 * rows, 384 * cols), dtype=np.uint8)
        images = []
        image, x, y, new_radius = self.circular_external_zero(image, raw_image, i)
        images.append(image)
        images.append(self.compute_histogram(image, x, y, new_radius))
        images.append(self.compute_gradient(image))
        images.append(self.filter_DCT(image, self.band_ratio_spinbox.value(), self.band_grade_spinbox.value(), "band"))
        images.append(self.filter_DCT(image, self.high_ratio_spinbox.value(), self.high_grade_spinbox.value(), "high"))
        images.append(self.filter_DCT(image, self.low_ratio_spinbox.value(), self.low_grade_spinbox.value(), "low"))
        h, w = self.images[0].shape
        for i, img in enumerate(images):
            combined_image[0:h, i * w:(i + 1) * w] = img
        return combined_image

    def combine_all_images(self):
        cols = 6
        rows = len(self.images)
        output_images = np.zeros((288 * rows, 384 * cols), dtype=np.uint8)
        if len(self.images) > 0:
            h, w = self.images[0].shape
            for i, img in enumerate(self.images):
                raw_img = img
                img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
                one_raw_image = self.combine_images(img, raw_img , i)
                h, w = one_raw_image.shape
                output_images[i * h:(i + 1) * h, 0:w] = one_raw_image
        return output_images

    def update_image(self):
        combined_image = self.combine_all_images()
        h, w = combined_image.shape
        q_image = QtGui.QImage(combined_image.data, w, h, w, QtGui.QImage.Format_Grayscale8)
        item = QtWidgets.QGraphicsPixmapItem(QtGui.QPixmap.fromImage(q_image))
        # 清空场景并添加新的 QPixmapItem
        self.scene.clear()
        self.scene.addItem(item)
        self.graphicsView.setScene(self.scene)
        self.graphicsView.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
        # self.graphicsView.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Widget = Analysis()
    Widget.show()
    sys.exit(app.exec_())
